from datetime import date, timedelta

# encoding:
# from gregorian year 1900 to 2049
# take 2009 for example:
# 0x0	c	a	b	5
#   0	1100	1010	1011	5 is the intercalary month
year_code = [
    					0x04bd8,	# 1900 
    0x04ae0, 0x0a570, 0x054d5, 0x0d260, 0x0d950,	# 1905 
    0x16554, 0x056a0, 0x09ad0, 0x055d2, 0x04ae0,	# 1910 
    0x0a5b6, 0x0a4d0, 0x0d250, 0x1d255, 0x0b540,	# 1915 
    0x0d6a0, 0x0ada2, 0x095b0, 0x14977, 0x04970,	# 1920 
    0x0a4b0, 0x0b4b5, 0x06a50, 0x06d40, 0x1ab54,	# 1925 
    0x02b60, 0x09570, 0x052f2, 0x04970, 0x06566,	# 1930 
    0x0d4a0, 0x0ea50, 0x06e95, 0x05ad0, 0x02b60,	# 1935 
    0x186e3, 0x092e0, 0x1c8d7, 0x0c950, 0x0d4a0,	# 1940 
    0x1d8a6, 0x0b550, 0x056a0, 0x1a5b4, 0x025d0,	# 1945 
    0x092d0, 0x0d2b2, 0x0a950, 0x0b557, 0x06ca0,	# 1950 
    0x0b550, 0x15355, 0x04da0, 0x0a5b0, 0x14573,	# 1955 
    0x052b0, 0x0a9a8, 0x0e950, 0x06aa0, 0x0aea6,	# 1960 
    0x0ab50, 0x04b60, 0x0aae4, 0x0a570, 0x05260,	# 1965 
    0x0f263, 0x0d950, 0x05b57, 0x056a0, 0x096d0,	# 1970 
    0x04dd5, 0x04ad0, 0x0a4d0, 0x0d4d4, 0x0d250,	# 1975 
    0x0d558, 0x0b540, 0x0b6a0, 0x195a6, 0x095b0,	# 1980 
    0x049b0, 0x0a974, 0x0a4b0, 0x0b27a, 0x06a50,	# 1985 
    0x06d40, 0x0af46, 0x0ab60, 0x09570, 0x04af5,	# 1990 
    0x04970, 0x064b0, 0x074a3, 0x0ea50, 0x06b58,	# 1995 
    0x055c0, 0x0ab60, 0x096d5, 0x092e0, 0x0c960,	# 2000 
    0x0d954, 0x0d4a0, 0x0da50, 0x07552, 0x056a0,	# 2005 
    0x0abb7, 0x025d0, 0x092d0, 0x0cab5, 0x0a950,	# 2010 
    0x0b4a0, 0x0baa4, 0x0ad50, 0x055d9, 0x04ba0,	# 2015 
    0x0a5b0, 0x15176, 0x052b0, 0x0a930, 0x07954,	# 2020 
    0x06aa0, 0x0ad50, 0x05b52, 0x04b60, 0x0a6e6,	# 2025 
    0x0a4e0, 0x0d260, 0x0ea65, 0x0d530, 0x05aa0,	# 2030 
    0x076a3, 0x096d0, 0x04bd7, 0x04ad0, 0x0a4d0,	# 2035 
    0x1d0b6, 0x0d250, 0x0d520, 0x0dd45, 0x0b5a0,	# 2040 
    0x056d0, 0x055b2, 0x049b0, 0x0a577, 0x0a4b0,	# 2045 
    0x0aa50, 0x1b255, 0x06d20, 0x0ada0			# 2049 
]

# big or small month
luni_month_days = [29, 30]

# returns a tuple like
# `(388, [29, 30, 30, ..., 29], 5)'
#  (this has 388 days, [this month has 29(30) days], the leap month is 5 (starts from 1))
def luni_parse_year_code(year):
    days, code = 0, year_code[year]
    leap_month = code &0xf # no lunar leap month if 0
    code >>= 4
    months = range(12)
    for i in range(12):
        days += luni_month_days[code &0x1]
        months[-i-1] = luni_month_days[code &0x1]
        code >>= 1
    if leap_month>0:
        days += luni_month_days[code & 0x1]
        months.insert(leap_month, luni_month_days[code & 0x1])
    return (days, months, leap_month)

# 150 years of data for future reference
luni_days_by_year, luni_days_by_month, luni_intercalary = [], [], []
for y in  range(len(year_code)):
    days, months, leap = luni_parse_year_code(y)
    luni_days_by_year.append(days)
    luni_days_by_month.append(months)
    luni_intercalary.append(leap)

start_date = date(1900, 1, 31)
end_date = date(2050, 12, 22)

class lunisolar():
    def __init__(self, y, m, d, l):
        self.year, self.month, self.day, self.intercalary = (y, m, d, l)
    def __str__(self):
        return "%d-%d-%d, %d" % (self.year, self.month, self.day, self.intercalary)

def greg_days_between(from_date=start_date, to_date=end_date):
    return (to_date - from_date).days

def greg_to_luni(d, neat=True):
    days = greg_days_between(to_date=d)
    for year in range(len(luni_days_by_year)):
        days -= luni_days_by_year[year]
        if days < 0: break
    days += luni_days_by_year[year]
    for month in range(13):
        days -= luni_days_by_month[year][month]
        if days < 0: break
    days += luni_days_by_month[year][month]
    month +=1
    intercalary = False
    if luni_intercalary[year] >0 and luni_intercalary[year] < month:
        month -= 1
        if month == luni_intercalary[year]: intercalary = True
    return lunisolar(year+start_date.year, month, days+1, intercalary)

def luni_to_greg(lunisolar):
    days = lunisolar.day
    year = lunisolar.year -start_date.year
    for y in range(year): days += luni_days_by_year[y]
    month_range = lunisolar.month -1
    if lunisolar.intercalary < lunisolar.month:
        month_range += 1
    for m in range(month_range): days += luni_days_by_month[year][m]
    return start_date +timedelta(days -2)

if __name__ == '__main__':
    d = date.today()
    print "%s => %s" % (d, greg_to_luni(d))
